﻿using System;
using System.Collections.Generic;
using GeoAPI.Geometries;
using YJKGridTopologyCal.Algorithm;
using YJKGridTopologyCal.GeometriesGraph;
using YJKGridTopologyCal.Utilities;

namespace YJKGridTopologyCal.Operation.Overlay
{
	public class OverlayOp : GeometryGraphOperation
	{
		public static bool NodingValidatorDisabled { get; set; }

		public static IGeometry Overlay(IGeometry geom0, IGeometry geom1, SpatialFunction opCode)
		{
			return new OverlayOp(geom0, geom1).GetResultGeometry(opCode);
		}

		public static bool IsResultOfOp(Label label, SpatialFunction overlayOpCode)
		{
			Location location = label.GetLocation(0);
			Location location2 = label.GetLocation(1);
			return OverlayOp.IsResultOfOp(location, location2, overlayOpCode);
		}

		public static bool IsResultOfOp(Location loc0, Location loc1, SpatialFunction overlayOpCode)
		{
			if (loc0 == Location.Boundary)
			{
				loc0 = Location.Interior;
			}
			if (loc1 == Location.Boundary)
			{
				loc1 = Location.Interior;
			}
			switch (overlayOpCode)
			{
			case SpatialFunction.Intersection:
				return loc0 == Location.Interior && loc1 == Location.Interior;
			case SpatialFunction.Union:
				return loc0 == Location.Interior || loc1 == Location.Interior;
			case SpatialFunction.Difference:
				return loc0 == Location.Interior && loc1 > Location.Interior;
			case SpatialFunction.SymDifference:
				return (loc0 == Location.Interior && loc1 != Location.Interior) || (loc0 != Location.Interior && loc1 == Location.Interior);
			default:
				return false;
			}
		}

		public OverlayOp(IGeometry g0, IGeometry g1) : base(g0, g1)
		{
			this._graph = new PlanarGraph(new OverlayNodeFactory());
			this._geomFact = g0.Factory;
		}

		public IGeometry GetResultGeometry(SpatialFunction overlayOpCode)
		{
			this.ComputeOverlay(overlayOpCode);
			return this._resultGeom;
		}

		public PlanarGraph Graph
		{
			get
			{
				return this._graph;
			}
		}

		private void ComputeOverlay(SpatialFunction opCode)
		{
			this.CopyPoints(0);
			this.CopyPoints(1);
			this.arg[0].ComputeSelfNodes(base.lineIntersector, false);
			this.arg[1].ComputeSelfNodes(base.lineIntersector, false);
			this.arg[0].ComputeEdgeIntersections(this.arg[1], base.lineIntersector, true);
			IList<Edge> list = new List<Edge>();
			this.arg[0].ComputeSplitEdges(list);
			this.arg[1].ComputeSplitEdges(list);
			this.InsertUniqueEdges(list);
			this.ComputeLabelsFromDepths();
			this.ReplaceCollapsedEdges();
			if (!OverlayOp.NodingValidatorDisabled)
			{
				new EdgeNodingValidator(this._edgeList.Edges).CheckValid();
			}
			this._graph.AddEdges(this._edgeList.Edges);
			this.ComputeLabelling();
			this.LabelIncompleteNodes();
			this.FindResultAreaEdges(opCode);
			this.CancelDuplicateResultEdges();
			PolygonBuilder polygonBuilder = new PolygonBuilder(this._geomFact);
			polygonBuilder.Add(this._graph);
			this._resultPolyList = polygonBuilder.Polygons;
			LineBuilder lineBuilder = new LineBuilder(this, this._geomFact, this._ptLocator);
			this._resultLineList = lineBuilder.Build(opCode);
			PointBuilder pointBuilder = new PointBuilder(this, this._geomFact, this._ptLocator);
			this._resultPointList = pointBuilder.Build(opCode);
			this._resultGeom = this.ComputeGeometry(this._resultPointList, this._resultLineList, this._resultPolyList, opCode);
		}

		private void InsertUniqueEdges(IEnumerable<Edge> edges)
		{
			foreach (Edge e in edges)
			{
				this.InsertUniqueEdge(e);
			}
		}

		protected void InsertUniqueEdge(Edge e)
		{
			Edge edge = this._edgeList.FindEqualEdge(e);
			if (edge != null)
			{
				Label label = edge.Label;
				Label label2 = e.Label;
				if (!edge.IsPointwiseEqual(e))
				{
					label2 = new Label(e.Label);
					label2.Flip();
				}
				Depth depth = edge.Depth;
				if (depth.IsNull())
				{
					depth.Add(label);
				}
				depth.Add(label2);
				label.Merge(label2);
				return;
			}
			this._edgeList.Add(e);
		}

		private void ComputeLabelsFromDepths()
		{
			foreach (Edge edge in this._edgeList)
			{
				Label label = edge.Label;
				Depth depth = edge.Depth;
				if (!depth.IsNull())
				{
					depth.Normalize();
					for (int i = 0; i < 2; i++)
					{
						if (!label.IsNull(i) && label.IsArea() && !depth.IsNull(i))
						{
							if (depth.GetDelta(i) == 0)
							{
								label.ToLine(i);
							}
							else
							{
								Assert.IsTrue(!depth.IsNull(i, Positions.Left), "depth of Left side has not been initialized");
								label.SetLocation(i, Positions.Left, depth.GetLocation(i, Positions.Left));
								Assert.IsTrue(!depth.IsNull(i, Positions.Right), "depth of Right side has not been initialized");
								label.SetLocation(i, Positions.Right, depth.GetLocation(i, Positions.Right));
							}
						}
					}
				}
			}
		}

		private void ReplaceCollapsedEdges()
		{
			List<Edge> list = new List<Edge>();
			List<Edge> list2 = new List<Edge>();
			foreach (Edge edge in this._edgeList)
			{
				if (edge.IsCollapsed)
				{
                    //IEnumerator<Edge> enumerator ;
                    //list2.Add(enumerator.Current);
					list.Add(edge.CollapsedEdge);
				}
			}
			foreach (Edge e in list2)
			{
				this._edgeList.Remove(e);
			}
			foreach (Edge e2 in list)
			{
				this._edgeList.Add(e2);
			}
		}

		private void CopyPoints(int argIndex)
		{
			IEnumerator<Node> nodeEnumerator = this.arg[argIndex].GetNodeEnumerator();
			while (nodeEnumerator.MoveNext())
			{
				Node node = nodeEnumerator.Current;
				this._graph.AddNode(node.Coordinate).SetLabel(argIndex, node.Label.GetLocation(argIndex));
			}
		}

		private void ComputeLabelling()
		{
			foreach (Node node in this._graph.Nodes)
			{
				node.Edges.ComputeLabelling(this.arg);
			}
			this.MergeSymLabels();
			this.UpdateNodeLabelling();
		}

		private void MergeSymLabels()
		{
			foreach (Node node in this._graph.Nodes)
			{
				((DirectedEdgeStar)node.Edges).MergeSymLabels();
			}
		}

		private void UpdateNodeLabelling()
		{
			foreach (Node node in this._graph.Nodes)
			{
				Label label = ((DirectedEdgeStar)node.Edges).Label;
				node.Label.Merge(label);
			}
		}

		private void LabelIncompleteNodes()
		{
			foreach (Node node in this._graph.Nodes)
			{
				Label label = node.Label;
				if (node.IsIsolated)
				{
					if (label.IsNull(0))
					{
						this.LabelIncompleteNode(node, 0);
					}
					else
					{
						this.LabelIncompleteNode(node, 1);
					}
				}
				((DirectedEdgeStar)node.Edges).UpdateLabelling(label);
			}
		}

		private void LabelIncompleteNode(GraphComponent n, int targetIndex)
		{
			Location location = this._ptLocator.Locate(n.Coordinate, this.arg[targetIndex].Geometry);
			n.Label.SetLocation(targetIndex, location);
		}

		private void FindResultAreaEdges(SpatialFunction opCode)
		{
			foreach (EdgeEnd edgeEnd in this._graph.EdgeEnds)
			{
				DirectedEdge directedEdge = (DirectedEdge)edgeEnd;
				Label label = directedEdge.Label;
				if (label.IsArea() && !directedEdge.IsInteriorAreaEdge && OverlayOp.IsResultOfOp(label.GetLocation(0, Positions.Right), label.GetLocation(1, Positions.Right), opCode))
				{
					directedEdge.InResult = true;
				}
			}
		}

		private void CancelDuplicateResultEdges()
		{
			foreach (EdgeEnd edgeEnd in this._graph.EdgeEnds)
			{
				DirectedEdge directedEdge = (DirectedEdge)edgeEnd;
				DirectedEdge sym = directedEdge.Sym;
				if (directedEdge.IsInResult && sym.IsInResult)
				{
					directedEdge.InResult = false;
					sym.InResult = false;
				}
			}
		}

		public bool IsCoveredByLA(Coordinate coord)
		{
			return this.IsCovered(coord, this._resultLineList) || this.IsCovered(coord, this._resultPolyList);
		}

		public bool IsCoveredByA(Coordinate coord)
		{
			return this.IsCovered(coord, this._resultPolyList);
		}

		private bool IsCovered(Coordinate coord, IEnumerable<IGeometry> geomList)
		{
			foreach (IGeometry geom in geomList)
			{
				if (this._ptLocator.Locate(coord, geom) != Location.Exterior)
				{
					return true;
				}
			}
			return false;
		}

		private IGeometry ComputeGeometry(IEnumerable<IGeometry> resultPtList, IEnumerable<IGeometry> resultLiList, IEnumerable<IGeometry> resultPlList, SpatialFunction opCode)
		{
			List<IGeometry> list = new List<IGeometry>();
			list.AddRange(resultPtList);
			list.AddRange(resultLiList);
			list.AddRange(resultPlList);
			if (list.Count == 0)
			{
				return OverlayOp.CreateEmptyResult(opCode, this.arg[0].Geometry, this.arg[1].Geometry, this._geomFact);
			}
			return this._geomFact.BuildGeometry(list);
		}

		public static IGeometry CreateEmptyResult(SpatialFunction overlayOpCode, IGeometry a, IGeometry b, IGeometryFactory geomFact)
		{
			IGeometry result = null;
			switch (OverlayOp.ResultDimension(overlayOpCode, a, b))
			{
			case Dimension.False:
				result = geomFact.CreateGeometryCollection(new IGeometry[0]);
				break;
			case Dimension.Point:
                //result = geomFact.CreatePoint(null);
				result = null;
				break;
			case Dimension.Curve:
                //result = geomFact.CreateLineString(null);
                result = null;
				break;
			case Dimension.Surface:
				result = geomFact.CreatePolygon(null, null);
				break;
			}
			return result;
		}

		private static Dimension ResultDimension(SpatialFunction opCode, IGeometry g0, IGeometry g1)
		{
			int dimension = (int)g0.Dimension;
			int dimension2 = (int)g1.Dimension;
			int result = -1;
			switch (opCode)
			{
			case SpatialFunction.Intersection:
				result = Math.Min(dimension, dimension2);
				break;
			case SpatialFunction.Union:
				result = Math.Max(dimension, dimension2);
				break;
			case SpatialFunction.Difference:
				result = dimension;
				break;
			case SpatialFunction.SymDifference:
				result = Math.Max(dimension, dimension2);
				break;
			}
			return (Dimension)result;
		}

		private readonly PointLocator _ptLocator = new PointLocator();

		private readonly IGeometryFactory _geomFact;

		private IGeometry _resultGeom;

		private readonly PlanarGraph _graph;

		private readonly EdgeList _edgeList = new EdgeList();

		private IList<IGeometry> _resultPolyList = new List<IGeometry>();

		private IList<IGeometry> _resultLineList = new List<IGeometry>();

		private IList<IGeometry> _resultPointList = new List<IGeometry>();
	}
}
